package search.searchtechniques;

import gpinterpreter.Instruction;
import gpinterpreter.vector.VecInstruction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import main.ProgressStatus;
import org.jgap.Gene;
import org.jgap.IChromosome;
import org.jgap.InvalidConfigurationException;
import org.jgap.impl.FixedBinaryGene;
import org.jgap.impl.IntegerGene;
import primitives.cluster.ClusterHead;
import search.fitnessfunctions.TreeFitnessFunction;
import search.genes.BitFieldInstructionGene;
import search.genes.GeneType;
import search.genes.InstructionGene;
import search.genes.IntegerInstructionGene;
import search.genes.StackInstructionGene;
import search.util.SearchResult;

public abstract class Search {

    protected GeneType geneType;
    protected int chromesize;
    protected int iterations;
    protected int gens;
    protected TreeFitnessFunction ff;
    protected ClusterHead in;
    protected String name = "";
    protected ProgressStatus progressStatus;
    protected String interpreter;
    protected String experimentName;
    protected double bestFitness;
    protected ClusterHead solution;

    public void setExperimentName(String en) {
        experimentName = en;
    }

    public int getChromosomeSize() {
        return chromesize;
    }

    public void setChromosomeSize(int chromesize) {
        this.chromesize = chromesize;
    }

    public TreeFitnessFunction getFitnessFunction() {
        return ff;
    }

    public void setFitnessFunction(TreeFitnessFunction ff) {
        this.ff = ff;
    }

    public String getGeneType() {
        return geneType.toString();
    }

    public void setGeneType(String geneType) {
        try {
            this.geneType = GeneType.valueOf(geneType);
        } catch (IllegalArgumentException iae) {
            HashMap<String, GeneType> mapping = new HashMap<String, GeneType>() {
                {
                    put("StackInstructionGene", GeneType.STACK);
                    put("IntegerInstructionGene", GeneType.INTEGER);
                    put("SymbolInstructionGene", GeneType.SYMBOL);
                    put("BitFieldInstructionGene", GeneType.BITFIELD);
                }
            };
            this.geneType = mapping.get(geneType);

        }
        if (this.geneType == null) {
            throw new RuntimeException("Gene Type of \"geneType\" is not recognised.");
        }
    }

    public int getGenerations() {
        return gens;
    }

    public void setGenerations(int gens) {
        this.gens = gens;
    }

    public ClusterHead getInput() {
        return in;
    }

    public void setInput(ClusterHead in) {
        this.in = in;
    }

    public int getIterations() {
        return iterations;
    }

    public void setIterations(int iterations) {
        this.iterations = iterations;
    }

    public int getPopulationSize() {
        return popsize;
    }

    public void setPopulationSize(int popsize) {
        this.popsize = popsize;
    }

    public void setFitnessBudget(int fb) {
        fitnessBudget = fb;
    }

    public int getFitnessBudget() {
        return fitnessBudget;
    }
    protected int popsize;

    public void setInterpreter(String interpreter) {
        this.interpreter = interpreter;
    }
    protected int fitnessBudget = -1;

    public Search() {
    }

    public Search(int population, int chromosomes, int generations,
            TreeFitnessFunction fitnessFunction, ClusterHead input) {
        popsize = population;
        chromesize = chromosomes;
        gens = generations;

        ff = fitnessFunction;

        in = input;
        geneType = GeneType.INTEGER;
    }

    public Search(int population, int chromosomes, int generations,
            TreeFitnessFunction fitnessFunction, ClusterHead input,
            String geneType) {
        this(population, chromosomes, generations, fitnessFunction, input);

        this.setGeneType(geneType);
    }

    public void setProgressStatus(ProgressStatus ps) {
        progressStatus = ps;
    }

    public void setName(String n) {
        name = n;
    }

    public static List<Instruction> getProgram(List<Gene> individual, GeneType geneType, ClusterHead in) {
        List<Instruction> program = new ArrayList<Instruction>();

        for (int i = 0; i < individual.size(); i++) {
            Instruction inst = null;
            switch (geneType) {
                case INTEGER:
                    IntegerGene g = (IntegerGene) (individual.get(i));
                    inst = IntegerInstructionGene.getInstruction(g.intValue(),
                            in.getSize());
                    break;
                case BITFIELD:
                    FixedBinaryGene fbg = (FixedBinaryGene) (individual.get(i));
                    inst = BitFieldInstructionGene.getInstruction(
                            BitFieldInstructionGene.asInteger(fbg), in.getSize());
                    break;
                case SYMBOL:
                    inst = (VecInstruction) individual.get(i).getAllele();
                    break;
                case STACK:
                    inst = ((StackInstructionGene) (individual.get(i).getAllele())).getInstruction();
                    break;
                default:
                    throw new RuntimeException("Gene type \"" + geneType + "\" isn't an instruction and can't be translated into a program.");
            }


            program.add(inst);
        }
        return program;
    }

    public static List<Instruction> getProgram(IChromosome individual, GeneType geneType, ClusterHead in) {
        List<Gene> conv = Arrays.asList(individual.getGenes());
        return getProgram(conv, geneType, in);

    }

    /**
     * Initialise the search, if necessary.
     */
    protected void setup() throws Exception {
    }

    /**
     * Run one iteration of the search.
     *
     * @return the best fitness reached so far by the search.
     */
    protected abstract double iteration();

    protected abstract ClusterHead getClustering();

    protected abstract double getFitness();

    public final SearchResult start() throws Exception {

        ff.setTree(in);
        ff.setFitnessBudget(fitnessBudget);
        
        //FIXME: what chromosome size should be used for the stack?
        //n isn't enough, 2n? that allows for everything to be pushed and clustered once.
        if(geneType == GeneType.HCLUST){
            chromesize = in.getSize() + 2*(in.getSize() - 1);
        }else if(geneType == GeneType.STACK){
            chromesize = in.getSize() * 2;
        }else{
            chromesize = in.getSize();
        }

        setup();
        int i = 0;
        progressStatus.start(experimentName + " " + name);
        while ((gens == 0 || i < gens) && !ff.budgetHit()) {
            bestFitness = iteration();
            i++;
        }
        String reason = "Iteration limit";

        ClusterHead tr;
        int totalEvals = 0;
        double fitness = 0;

        if (ff.budgetHit()) {
            reason = "Budget reached";
            tr = ff.getFittest();
            fitness = ff.getBestFitness();
            totalEvals = ff.getEvalsToBest();
        } else {
            tr = getClustering();
            fitness = getFitness();
            totalEvals = ff.getFitnessEvaluations();
        }


        Logger.getLogger(Search.class.getName()).log(Level.INFO, "Clustering of {0} complete, fitness {1} after {2} evals. {3}", new Object[]{name, fitness, totalEvals, reason});


        progressStatus.finish(fitness);

        SearchResult ret = new SearchResult(tr, fitness);
        ret.setFitnessEvaluations(totalEvals);

        return ret;
    }
}